﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace EPPlayer
{
    /// <summary>
    /// The Resource Manager holds most of our data processing and collections
    /// of all the game world classes.
    /// The game rules are contained in an XML file. Resource manager deals with reading
    /// and parsing that file. Some of the rules are kept on hand as xml fragments and
    /// consumed at a later stage.
    /// Improvment: All the collections are r/w although are not meant to be written to.
    /// It also wound't be thread safe to do so. Only place writing should be our constructor
    /// (but this is not enforced currently)
    /// </summary>

    public static class GameResources
    {
        public static Dictionary<string, string> AttributeLongNames = new Dictionary<string, string>
        {
            {"COG", "Cognition"},
            {"COO", "Coordination"},
            {"INT", "Intuition"},
            {"REF", "Reflexes"},
            {"SAV", "Savvy"},
            {"SOM", "Somatics"},
            {"WIL", "Willpower"},
            {"MOX", "Moxie"},
            {"CRED","Credits"},
            {"SPD", "Speed"},
            {"DUR", "Durability"}
        };

        public static Dictionary<string, Aptitude> Aptitudes = new Dictionary<string, Aptitude>();
        public static Dictionary<string, Stat> Stats = new Dictionary<string, Stat>();
        public static Dictionary<string, Skill> Skills = new Dictionary<string, Skill>();
        public static Dictionary<string, Reputation> Reputations = new Dictionary<string, Reputation>();
        public static Dictionary<string, Background> Backgrounds = new Dictionary<string, Background>();
        public static Dictionary<string, Faction> Factions = new Dictionary<string, Faction>();
        public static Dictionary<string, Trait> Traits = new Dictionary<string, Trait>();
        public static Dictionary<string, Morph> Morphs = new Dictionary<string, Morph>();
        public static Dictionary<string, Gear> Gear = new Dictionary<string, Gear>();

        static GameResources()
        {
            var CoreRules = XElement.Load(@"GameResources\CoreRules-jsenek.xml");
            var AdditionalRules = XElement.Load(@"GameResources\Aptitudes.xml");

            // Some skills have a Field enumeration with them, for example
            // Interest: Martian Beers and Interest: Old Earth.
            // Convert them to indivisual skills

            foreach (XElement xe in CoreRules.Descendants("skills").Descendants("skill"))
            {
                var Fields = xe.Descendants("field");
                if (Enumerable.Count(Fields) > 0)
                {
                    foreach (var xeField in Fields)
                    {
                        var SkillName = string.Format("{0}: {1}",
                            xe.Element("name").Value, xeField.Value);
                        Skills.Add(SkillName,
                            new Skill(
                            SkillName,
                            AttributeLongNames[xe.Element("apt").Value],
                            xe.Element("defaulting").Value == "Yes" ? true : false,
                            xe.Element("category").Value,
                            xe.Element("name").Value,               // add the skill name proper as group
                            "<Skill description>"));
                    }
                }
                else
                {
                    var SkillName = xe.Element("name").Value;
                    Skills.Add(SkillName,
                        new Skill(
                        SkillName,
                        AttributeLongNames[xe.Element("apt").Value],
                        xe.Element("defaulting").Value == "Yes" ? true : false,
                        xe.Element("category").Value,
                        xe.Element("group").Value,
                        "<Skill description>"));
                }
            }

            foreach (var xElement in AdditionalRules.Descendants("Rep"))
            {
                var name = xElement.Attribute("Name").Value;
                Reputations.Add(name,
                    new Reputation(
                    name,
                    xElement.Element("Description").Value));
            }
 
            foreach (var xElement in AdditionalRules.Descendants("Aptitudes").Descendants("Aptitude"))
            {
                var name = xElement.Attribute("Name").Value;
                Aptitudes.Add(name,
                    new Aptitude(
                    name,
                    xElement.Element("Shorthand").Value,
                    xElement.Element("Description").Value));
            }

            // Stats
            foreach (var xElement in AdditionalRules.Descendants("Stats").Descendants("Stat"))
            {
                var name = xElement.Attribute("Name").Value; 
                Stats.Add(name, 
                    new Stat(
                    name,
                    xElement.Element("Description").Value));
            }

            // Backgrounds
            foreach (var xElement in CoreRules.Descendants("backgrounds").Descendants("background"))
            {
                var Name = xElement.Element("name").Value;
                var Description = xElement.Element("description").Value;
                var Mods = Modifiers.Parse(xElement);

                Backgrounds.Add(Name,
                    new Background(
                    Name,
                    xElement.Element("advantages").Element("text").Value,
                    xElement.Element("disadvantages").Element("text").Value,
                    xElement.Element("commonmorphs").Element("text").Value,
                    Mods,
                    Description));
            }

            // Traits
            var TraitChunks = new List<IEnumerable<XElement>>();
            TraitChunks.Add(CoreRules.Descendants("positiveTraits").Descendants("trait"));
            TraitChunks.Add(CoreRules.Descendants("negativeTraits").Descendants("trait"));

            var isPositiveTrait = true;
            foreach (var TraitChunk in TraitChunks)
            {
                foreach (var xElement in TraitChunk)
                {
                    var Name = xElement.Element("name").Value;
                    var Source = xElement.Element("source").Value;
                    var Description = xElement.Element("description").Value;
                    var Cost = Convert.ToInt32(xElement.Element("CP").Value);
                    if (isPositiveTrait == false)
                    {
                        Cost = -Cost;
                    }
                    var Mods = Modifiers.Parse(xElement);

                    Traits.Add(Name,
                        new Trait(
                        Name,
                        Source,
                        isPositiveTrait,
                        Cost,
                        Mods,
                        Description)); 
                }
                isPositiveTrait = false;
            }

            // Factions
            foreach (var xElement in CoreRules.Descendants("factions").Descendants("faction"))
            {
                var Name = xElement.Element("name").Value;
                var Description = xElement.Element("description").Value;
                var Mods = Modifiers.Parse(xElement);

                Factions.Add(Name,
                    new Faction(
                    Name,
                    xElement.Element("advantages").Element("text").Value,
                    xElement.Element("disadvantages").Element("text").Value,
                    Mods,
                    Description));
            }

            // Morphs
            foreach (var xElement in CoreRules.Descendants("morphs").Descendants("morph"))
            {
                var Name = xElement.Element("name").Value;
                var Description = xElement.Element("description").Value;
                var Mods = Modifiers.Parse(xElement);

                var Sleeve = new Morph(
                    Name,
                    xElement.Element("type").Value,
                    xElement.Element("advantages").Element("text").Value,
                    xElement.Element("disadvantages").Element("text").Value,
                    Convert.ToInt32(xElement.Element("durability").Value),
                    Convert.ToInt32(xElement.Element("CPCost").Value),
                    Mods,
                    Description);
                foreach (var Implant in xElement.Descendants("implants").Descendants("implant"))
                {
                    Sleeve.Implants.Add(Implant.Value);
                }
                Morphs.Add(Name, Sleeve);
            }

            // Funnel all the different gear down the gear parser
            var ParseList2 = new Dictionary<string, IEnumerable<XElement>>();
            ParseList2.Add("Augmentation", CoreRules.Descendants("gearAndEquipment").Descendants("augmentation"));
            ParseList2.Add("Armor", CoreRules.Descendants("otherGear").Descendants("armor"));
            ParseList2.Add("ArmorAccessory", CoreRules.Descendants("gearAndEquipment").Descendants("armorAccessory"));
            ParseList2.Add("ArmorMod", CoreRules.Descendants("gearAndEquipment").Descendants("armorMod"));
            ParseList2.Add("Gear", CoreRules.Descendants("gearAndEquipment").Descendants("gear"));
            foreach (var ParseChunk in ParseList2)
            {
                foreach (var xElement in ParseChunk.Value)
                {
                    var Name = xElement.Element("name").Value;
                    var Description = xElement.Element("description").Value;
                    var CreditCost = xElement.Element("creditCost").Value;
                    int KineticArmor = 0, EnergyArmor = 0;
                    var Mods = Modifiers.Parse(xElement);
                    if (xElement.Element("energy") != null)
                    {
                        EnergyArmor = Convert.ToInt32(xElement.Element("energy").Value);
                    }
                    if (xElement.Element("kinetic") != null)
                    {
                        KineticArmor = Convert.ToInt32(xElement.Element("kinetic").Value);
                    }
                    Gear.Add(Name,
                        new Gear(Name, ParseChunk.Key, CreditCost, KineticArmor, EnergyArmor,
                        Mods, Description));
                }
            }
        }

        /// <summary>
        /// Apply modifiers contained in the provided xml fragment on behalf of an Attribute
        /// Improvement: Aptitude limiters are applied last in this function, and they will only
        /// work correctly when they run last, but this is not enforced properly.
        /// </summary>

        public static void AttachModifiers(Entity Entity, Attribute Source, Modifiers Mods)
        {
            foreach (XElement xe in Mods.Aptitude.Where(el => el.Name == "aptmod"))
            {
                string name = xe.Element("name").Value;
                string amount = xe.Element("amount").Value;
                int Modifier = Convert.ToInt32(amount);
                if (name == "Choice")
                {
                    AttributeFilter Af = new AttributeFilter(Source.name, Source.color, Source,
                        Entity["CP"], value => value + (10 * Modifier), "CP Compensation");
                    Af.Owner = Source;
                    Entity.Add(Af);
                }
                else
                {
                    name = AttributeLongNames[name];
                    Entity.AddAdditiveFilter(Source, name, Modifier);
                }
            }
            foreach (XElement xe in Mods.AptitudeMax.Elements().Where(el => AttributeLongNames.ContainsKey(el.Name.ToString())))
            {
                string name = xe.Name.ToString();
                name = AttributeLongNames[name];
                Int32 Max = Convert.ToInt32(xe.Value);
                // for some reason, math.max was NOT doing anything in this lambda
                AttributeFilter Af = new AttributeFilter(Source.name, Source.color, Source,
                    Entity[name], Num => Num > Max ? Max : Num,"Aptitude maximum");
                Af.Owner = Source;
                Entity.Add(Af);
            }
            foreach (XElement xe in Mods.Skill.Where(el => el.Name == "skillmod"))
            {
                string name = xe.Element("name").Value;
                if (xe.Element("amount") != null)
                {
                    string amount = xe.Element("amount").Value;
                    if (xe.Element("field") != null)
                    {
                        name = string.Format("{0}: {1}", name, xe.Element("field").Value);
                    }
                    int Modifier = Convert.ToInt32(amount);
                    if (name == "Choice")
                    {
                        // sometimes the user gets to choose which skills to apply a bonus to.
                        // for now, we'll give extra CP to spend
                        int Quantity = 1;
                        if (xe.Element("quantity") != null)
                        {
                            Quantity = Convert.ToInt32(xe.Element("quantity").Value);
                        }
                        AttributeFilter Af = new AttributeFilter(Source.name, Source.color, Source,
                            Entity["CP"], value => value + (Quantity * Modifier), "CP Compensation");
                        Af.Owner = Source;
                        Entity.Add(Af);
                    }
                    else
                    {
                        Entity.AddAdditiveFilter(Source, name, Modifier);
                    }
                }
                else
                {
                    // todo: skillmod with no amount
                }
            }
            foreach (XElement xe in Mods.Stat.Where(el => el.Name == "statmod"))
            {
                string name = xe.Element("name").Value;
                if (name == "Choice")
                {
                    // sometimes the user gets to choose which skills to apply a bonus to.
                    // todo: for stats, costs are variable, so this needs to be implemented with a callback query

                    throw new NotImplementedException("Sorry!");
                }
                else if (name == "REP")
                {
                    // todo: currently cannot process the Isolate -10 starting rep disadvantage
                    continue;
                }

                if (xe.Element("amount") != null)
                {
                    name = AttributeLongNames[name];
                    int Modifier = Convert.ToInt32(xe.Element("amount").Value);
                    Entity.AddAdditiveFilter(Source, name, Modifier);
                }
                else
                {
                    // todo: statmod with no amount
                    throw new NotImplementedException("Sorry!");
                }
            }
        }

        /* Character creation and point costs:
            Active skill minimum: 400 skill points
            Knowledge skill minimum: 300 skill points
            Choose Starting Morph (pp. 136 and 139)
            Choose Traits (pp. 136 and 145)
            Purchase Gear (p. 136)
            Choose Motivation (p. 137)
            Calculate Remaining Stats (p. 138)
            Detail the Character (p. 138)
        */

        public static Dictionary<string, int> CostAverage = new Dictionary<string, int>()
        {
            { "Trivial", 50 },
            { "Low", 250 },
            { "Moderate", 1000 },
            { "High", 5000 },
            { "Expensive", 20000 }
        };

        public static IEnumerable<Gear> Armors
        {
            get
            {
                return Gear.Values.Where(g => g.Type == "Armor").OrderBy(g => g.name);
            }
        }

        public static IEnumerable<string> Fields
        {
            get
            {
                return Skills.Values.Where(Sk => Sk.isFieldSkill == true).Select(Va => Va.name.Split(':').First()).Distinct().OrderBy(s => s);
            }
        }
    }
}